package viper.call.common;
/*
 * @(#)LiveStream.java	1.4 01/03/13
 *
 * Copyright (c) 1999-2001 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 */

/*****************************************
 * LiveStream.java
 * ----------------
 * Revision: v.1.0
 * Author: Ji Kim
 * Date Created: 12/13/2009
 * Description:
 * Last Back-Up:
 * Revision History: *Modified existing code
 * 					 *Duplex read/write capability
 * 					 *Added custom PushBufferStream functionality from continual supply of byte[] packets
 *****************************************/

import java.awt.Dimension;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

import javax.media.Buffer;
import javax.media.Control;
import javax.media.Format;
import javax.media.format.AudioFormat;
import javax.media.format.RGBFormat;
import javax.media.protocol.BufferTransferHandler;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.PushBufferStream;

import viper.main.StateManager;
import viper.receiver.UDPReceiver;

/**
 * @author Ji Kim
 * 
 * Revision number: 5
 * Author: Ji Kim
 * Date created: 12/25/09
 * Date of Last Back-up on SVN: 04/12/2010
 * 
 * Custom Audio Stream to handle reading and writing of received voice packets
 * 
 * Based on LiveStream.java template from official Java SDN.
 *  
 * Revision List:
 * 		- Changed Buffer Cycling Algorithm
 *        Added multiple codec switching
 *        Cases to handle Null packets and packet waiting
 * 		- Revising Author: Ji Kim
 * 		- Date Implemented: 04/05/2010
 */
public class LiveStream implements PushBufferStream, Runnable {

	protected ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW);
	protected int maxDataLength;
	protected byte [] data;
	protected Dimension size;
	protected RGBFormat rgbFormat;
	protected AudioFormat audioFormat;
	protected boolean started;
	public Thread thread;
	protected float frameRate = 20f;
	protected BufferTransferHandler transferHandler;
	protected Control [] controls = new Control[0];
	int marker = 0;
	int lastRead = 0;
	byte [][] rxbuffer;
	long [] timestamps;
	int rxbuffersize;
	boolean wrapped = false;

	protected boolean videoData = false;
	
	private Timer timer;

	public LiveStream() {
		
		//various codecs for testing, uncomment as needed
		audioFormat = new AudioFormat(AudioFormat.MPEG,
				48000,
				16,
				1,
				AudioFormat.BIG_ENDIAN,
				AudioFormat.SIGNED,
				8,
				8000.0,
				Format.byteArray);
		
		/**
		audioFormat = new AudioFormat(AudioFormat.ULAW,
				44100,
				8,
				2);
		**/
		/**
		audioFormat = new AudioFormat(AudioFormat.GSM,
				44100,
				16,
				1);
		**/
		/**
		audioFormat = new AudioFormat(AudioFormat.G723,
				  44100,
				  Format.NOT_SPECIFIED,
				  1,
				  Format.NOT_SPECIFIED,
				  Format.NOT_SPECIFIED,
				  192,
				  Format.NOT_SPECIFIED,
				  Format.byteArray);
		**/
		rxbuffersize = 500; //max buffer size to hold received packets
		maxDataLength = UDPReceiver.MAX_PACKET_SIZE;
		rxbuffer = new byte[rxbuffersize][];
		timestamps = new long[rxbuffersize];
		
		//variables for jumpstarting player when it stops reading
		timer = new Timer();
		timer.schedule(new jumpstartPlayer(),500);
		
		thread = new Thread(this);
	}

	/***************************************************************************
	 * SourceStream
	 ***************************************************************************/

	public ContentDescriptor getContentDescriptor() {
		return cd;
	}

	public long getContentLength() {
		return LENGTH_UNKNOWN;
	}

	public boolean endOfStream() {
		return false;
	}

	/***************************************************************************
	 * PushBufferStream
	 ***************************************************************************/

	int seqNo = 1;
	double freq = 2.0;

	public Format getFormat() {
		if (videoData)
			return rgbFormat;
		else
			return audioFormat;
	}
	
	public void clearBuffer(){
		marker = 0;
		lastRead = 0;
	}

	//add packet to buffer and organize
	public void update(byte[] block, long timestamp){
		/**
		System.out.println("=================================");
		System.out.println("Received buffer: "+block);
		System.out.println("Received Timestamp: "+timestamp);
		System.out.println("=================================");
		**/
		//case for resetting buffer index when buffer is completely filed (wraparound)
		if(marker==rxbuffersize){
			wrapped = true;
			marker=0;
		}
		//normal case, as long as buffer isn't filled completely, add to next index
		//drop any packets if buffer is full
		if((!wrapped) || (wrapped && marker!=lastRead)){
			rxbuffer[marker]=block;
			timestamps[marker]=timestamp;
			/** Debug-Ji
			if(block==null)
				System.out.println("WHY NULL?");
			**/
			marker++;
			//System.out.println("ADDED!+++++++++++++++++++++++++++++++++");
		}
		/** Debug-Ji
		System.out.println("UPDATE: "+marker+", "+lastRead);
		**/
	}

	//read the next packet that hasn't been read yet
	public void read(Buffer buffer) throws IOException {
		//reset timers every time we read successfully
		timer.cancel();
		timer = new Timer();
		//trigger jumpstart if we don't read for awhile
		timer.schedule(new jumpstartPlayer(), 500);
		synchronized (this) {
			/** Debug-Ji
			System.out.println("DEBUG: READ: "+marker+", "+lastRead);
			**/
			//lastRead keeps track of last-read packet and marker keeps track of latest received packet
			if(lastRead==rxbuffersize && marker!=rxbuffersize){
				wrapped = false;
				lastRead=0;
			}
			
			//grab reference to the buffer/array to be filled with newly read data
			Object outdata = buffer.getData();
			//if reading too fast, trying to read empty index
			if(lastRead==marker || rxbuffer[lastRead]==null){
				outdata = new byte[maxDataLength];
			}
			//otherwise, still unread packets waiting in buffer
			else{
				int temp = rxbuffer[lastRead].length;
				outdata = new byte[temp];
			}
			buffer.setData(outdata);
			buffer.setFormat( audioFormat );
			if(lastRead!=marker)
				buffer.setTimeStamp(timestamps[lastRead]);
			else
				buffer.setTimeStamp(1000000);
			//buffer.setTimeStamp( (long) seqNo * 1000000000 / 8 );
			if(lastRead!=marker && rxbuffer[lastRead]!=null)
				System.arraycopy(rxbuffer[lastRead], 0, outdata, 0, rxbuffer[lastRead].length);
			buffer.setSequenceNumber( seqNo );
			if(lastRead!=marker && rxbuffer[lastRead]!=null){
				buffer.setLength(rxbuffer[lastRead].length);
				//lastRead++;
			}
			else
				buffer.setLength(maxDataLength);
			if(lastRead!=marker)
				lastRead++;
			buffer.setFlags(32864);
			buffer.setHeader( null );
			seqNo++;
		}
	}

	public void setTransferHandler(BufferTransferHandler transferHandler) {
		synchronized (this) {
			this.transferHandler = transferHandler;
			notifyAll();
		}
	}

	public void start(boolean started) {
		synchronized ( this ) {
			this.started = started;
			if (started && !thread.isAlive()) {
				thread = new Thread(this);
				thread.start();
			}
			notifyAll();
		}
	}

	/***************************************************************************
	 * Runnable
	 ***************************************************************************/

	public void run() {
		while (started) {
			synchronized (this) {
				while (transferHandler == null && started) {
					try {
						wait(10);
					} catch (InterruptedException ie) {
						return;
					}
				} // while
			}

			if (started && transferHandler != null) {
				transferHandler.transferData(this);
				try {
					Thread.sleep(5);
				} catch (InterruptedException ise) {
					return;
				}
			}
		} // while (started)
	} // run

	// Controls

	public Object [] getControls() {
		return controls;
	}

	public Object getControl(String controlType) {
		try {
			Class<?>  cls = Class.forName(controlType);
			Object cs[] = getControls();
			for (int i = 0; i < cs.length; i++) {
				if (cls.isInstance(cs[i]))
					return cs[i];
			}
			return null;

		} catch (Exception e) {   // no such controlType or such control
			return null;
		}
	}
	
	public void stopTimer(){
		timer.cancel();
	}
	
	//jumpstart for when player stops reading
	class jumpstartPlayer extends TimerTask{
		public void run(){
			StateManager.INSTANCE.getCallReceiver().jumpstart();
		}
	}
}
